﻿using Swifter.Api.Types;
using Swifter.RW;
using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace Swifter.Api
{
    public sealed class ContextInfo : IDisposable
    {
        public const string DefaultCommandVersionConfigurationName = "Command_Release_Version";
        public const string DefaultRoleConfigurationName = "Default_Role_Id";

        internal readonly Guid validator_high;
        internal readonly Guid validator_low;

        /// <summary>
        /// 客户端应用程序（软件） Id。
        /// </summary>
        internal readonly Guid client_id;

        /// <summary>
        /// 客户端设备（硬件） Id。
        /// </summary>
        internal readonly Guid device_id;

        /// <summary>
        /// 客户端语言。
        /// </summary>
        internal readonly string language;

        /// <summary>
        /// 上一次执行处理时间。
        /// </summary>
        internal long last_process_timestamp;

        /// <summary>
        /// 上一次客户端的 IP 地址。
        /// </summary>
        internal string last_client_ip_address;

        public readonly BaseApplication Application;

        public Version CommandVersion { get; set; }

        public List<int> Roles { get; }

        internal ContextInfo(BaseApplication application, Guid clientId, Guid deviceId, string language, string ip_address)
        {
            validator_high = Guid.NewGuid();
            validator_low = Guid.NewGuid();

            Application = application;

            // 获取默认版本号
            CommandVersion = ValueInterface<Version>.ReadValue(application.GetGlobalParameter(DefaultCommandVersionConfigurationName));

            Roles = new List<int>
            {
                // 获取默认角色
                ValueInterface<int>.ReadValue(application.GetGlobalParameter(DefaultRoleConfigurationName))
            };

            last_process_timestamp = Stopwatch.GetTimestamp() - Application.ContextExpiredInterval.Ticks + Application.NewContextExpiredInterval.Ticks;
            last_client_ip_address = ip_address;

            client_id = clientId;
            device_id = deviceId;

            this.language = language;
        }


        public void Process(ContextParameters parameters, Action<object> callback)
        {
            if (last_client_ip_address != parameters.IP)
            {
                throw new VerifyClientException("IP address changes.");
            }

            last_process_timestamp = Stopwatch.GetTimestamp();
            last_client_ip_address = parameters.IP;

            var commandInfo = GetCommand(parameters.Command);

            if (CheckCommand(parameters, commandInfo))
            {
                if (!string.IsNullOrEmpty(commandInfo.LockKey))
                {
                    var lock_key = Application.ProcessKey(commandInfo.LockKey, this, parameters);

                    Application.EnterLock(lock_key);

                    invoke();

                    Application.ExitLock(lock_key);
                }
                else
                {
                    invoke();
                }

                void invoke()
                {
                    foreach (var item in commandInfo.BeforeTriggers)
                    {
                        if (GetCommand(item) is RuntimeCommandInfo beforeCommandInfo && CheckCommand(parameters, beforeCommandInfo))
                        {
                            var isAsync = true;

                            beforeCommandInfo.Invoker.Invoke(this, parameters, (result) =>
                            {
                                isAsync = false;
                            });

                            if (isAsync)
                            {
                                throw new DeveloperException("Triggers do not allow asynchronous interfaces.");
                            }
                        }
                    }

                    invoke((result) =>
                    {
                        foreach (var item in commandInfo.AfterTriggers)
                        {
                            if (GetCommand(item) is RuntimeCommandInfo beforeCommandInfo && CheckCommand(parameters, beforeCommandInfo))
                            {
                                var isAsync = true;

                                beforeCommandInfo.Invoker.Invoke(this, parameters, (result) =>
                                {
                                    isAsync = false;
                                });

                                if (isAsync)
                                {
                                    throw new DeveloperException("Triggers do not allow asynchronous interfaces.");
                                }
                            }
                        }

                        callback(result);
                    });

                    void invoke(Action<object> callback)
                    {
                        if (!string.IsNullOrEmpty(commandInfo.CacheKey))
                        {
                            var cache_key = Application.ProcessKey(commandInfo.CacheKey, this, parameters);

                            if (Application.GetCacheInfo(cache_key, parameters, out var cache_info))
                            {
                                callback(cache_info);
                            }
                            else
                            {
                                commandInfo.Invoker.Invoke(this, parameters, (result) =>
                                {
                                    var cache_copyer = new ValueCopyer();

                                    ValueInterface.WriteValue(cache_copyer, result);

                                    result = ValueInterface<object>.ReadValue(cache_copyer);

                                    Application.SetCacheInfo(cache_key, parameters, new RuntimeCacheInfo {
                                        CacheTime = DateTimeOffset.Now,
                                        Data = result
                                    });

                                    callback(result);
                                });
                            }
                        }
                        else
                        {
                            commandInfo.Invoker.Invoke(this, parameters, callback);
                        }
                    }
                }
            }
            else
            {
                throw new UnauthorizedException("Command is not authorized.");
            }
        }

        public RuntimeCommandInfo GetCommand(string commandName)
        {
            return Application.GetCommand(commandName, CommandVersion);
        }

        /// <summary>
        /// 检查命令信息。
        /// </summary>
        /// <param name="commandInfo">命令信息</param>
        /// <returns>返回是否允许运行</returns>
        public bool CheckCommand(ContextParameters parameters, RuntimeCommandInfo commandInfo)
        {
            if (commandInfo.NeedAuthorized)
            {
                foreach (var item in Roles)
                {
                    if (Application.CheckCommandAuthorization(item, commandInfo.Name))
                    {
                        goto Authorized;
                    }
                }

                return false;
            }

        Authorized:

            foreach (var item in commandInfo.parameters)
            {
                parameters.CommandParameters.TryGetValue(item.Key, out var valueCopyer);

                var valueInterface = ValueInterface.GetInterface(item.Value);

                var value = valueInterface.Read(valueCopyer);

                if (value is IValidator validator)
                {
                    validator.Verify(this, parameters, item.Key);
                }

                valueInterface.Write(valueCopyer, value);
            }

            return true;
        }

        public void Dispose()
        {
        }
    }
}
